package repository

import (
	"context"
	"github.com/ecodeclub/ekit/slice"
	"go.uber.org/zap"
	"gorm.io/gorm"
	cache2 "jk-time/webook/internal/cache"
	article2 "jk-time/webook/internal/dao/article"
	"jk-time/webook/internal/domain"
	"time"
)

type ArticleRepository interface {
	Create(ctx context.Context, art domain.Article) (int64, error)
	Update(ctx context.Context, art domain.Article) error
	Sync(ctx context.Context, art domain.Article) (int64, error)
	SyncStatus(ctx context.Context, art domain.Article) error
	List(ctx context.Context, author int64, offset, limit int) ([]domain.Article, error)
	ListPub(ctx context.Context, start time.Time, offset int, limit int) ([]domain.PublishedArticle, error)
	GetById(ctx context.Context, id int64) (domain.Article, error)

	GetPublishById(ctx context.Context, id int64) (domain.PublishedArticle, error)
}
type CacheArticleRepository struct {
	dao   article2.ArticleDao
	cache cache2.ArticleCache
	//SyncV1 用
	authorDAO article2.ArticleAuthorDAO
	readerDAO article2.ArticleReaderDAO

	// SyncV2 用
	db *gorm.DB

	userRepo UserRepository
}

func NewCacheArticleRepository(dao article2.ArticleDao, authorDAO article2.ArticleAuthorDAO,
	readerDAO article2.ArticleReaderDAO, db *gorm.DB, cache cache2.ArticleCache, userRepo UserRepository) ArticleRepository {
	return &CacheArticleRepository{dao: dao, authorDAO: authorDAO, readerDAO: readerDAO,
		db: db, cache: cache, userRepo: userRepo}
}
func NewCacheArticleRepositoryV2(dao article2.ArticleDao, cache cache2.ArticleCache,
	userRepo UserRepository) ArticleRepository {
	return &CacheArticleRepository{dao: dao, cache: cache, userRepo: userRepo}
}
func (c *CacheArticleRepository) Cache() cache2.ArticleCache {
	return c.cache
}
func (c *CacheArticleRepository) Create(ctx context.Context, art domain.Article) (int64, error) {
	defer func() {
		//清空缓存,并发下，延迟双删
		err := c.cache.DelFirstPage(ctx, art.Author.Id)
		if err != nil {
			zap.L().Error("清除缓存失败：", zap.Error(err))
		}
	}()
	return c.dao.Insert(ctx, c.toEntity(art))
}
func (c *CacheArticleRepository) Update(ctx context.Context, art domain.Article) error {
	defer func() {
		//清空缓存,并发下，延迟双删
		err := c.cache.DelFirstPage(ctx, art.Author.Id)
		if err != nil {
			zap.L().Error("清除缓存失败：", zap.Error(err))
		}
	}()
	return c.dao.UpdateById(ctx, c.toEntity(art))
}
func (c *CacheArticleRepository) Sync(ctx context.Context, art domain.Article) (int64, error) {
	defer func() {
		//清空缓存,并发下，延迟双删
		err := c.cache.DelFirstPage(ctx, art.Author.Id)
		if err != nil {
			zap.L().Error("清除缓存失败：", zap.Error(err))
		}

		err = c.cache.SetPub(ctx, domain.PublishedArticle(art))
		if err != nil {
			zap.L().Error("设置已发表缓存失败：", zap.Error(err))
		}
	}()
	return c.dao.Sync(ctx, c.toEntity(art))
}
func (c *CacheArticleRepository) SyncV2(ctx context.Context, art domain.Article) (int64, error) {
	var err error
	if art.Id > 0 {
		err = c.authorDAO.UpdateById(ctx, c.toEntity(art))
	} else {
		art.Id, err = c.authorDAO.Create(ctx, c.toEntity(art))
	}
	if err != nil {
		return 0, err
	}
	return art.Id, c.readerDAO.Upsert(ctx, article2.PublishedArticle(c.toEntity(art)))
}
func (c *CacheArticleRepository) SyncV1(ctx context.Context, art domain.Article) (int64, error) {
	tx := c.db.WithContext(ctx).Begin()
	if tx.Error != nil {
		return 0, tx.Error
	}
	defer tx.Rollback()

	readerDao := article2.NewGormArticleReaderDAO(tx)
	authorDao := article2.NewGormArticleAuthorDAO(tx)

	// 下面代码和 SyncV1 一模一样
	var err error
	if art.Id > 0 {
		err = authorDao.UpdateById(ctx, c.toEntity(art))
	} else {
		art.Id, err = authorDao.Create(ctx, c.toEntity(art))
	}
	if err != nil {
		return 0, err
	}
	err = readerDao.Upsert(ctx, article2.PublishedArticle(c.toEntity(art)))
	if err != nil {
		return 0, err
	}
	tx.Commit()
	return art.Id, nil
}

func (c *CacheArticleRepository) SyncStatus(ctx context.Context, art domain.Article) error {
	return c.dao.SyncStatus(ctx, c.toEntity(art))
}
func (c *CacheArticleRepository) List(ctx context.Context, author int64, offset, limit int) ([]domain.Article, error) {
	if offset < 0 {
		offset = 0
	}
	if limit < 0 {
		limit = 0
	}
	//只有100这个页大小的情况下，会走索引
	if offset == 0 && limit == 100 {
		data, err := c.cache.GetFirstPage(ctx, author)
		if err == nil {
			return data, nil
		}
		// 记录不存在等错误，走数据库
		if err != cache2.ErrKeyNotExist {
			zap.L().Error("获取缓存数据失败:", zap.Error(err))
		}
	}
	//走数据库，慢路径
	arts, err := c.dao.GetByAuthor(ctx, author, offset, limit)
	if err != nil {
		return nil, err
	}
	data := slice.Map[article2.Article, domain.Article](arts,
		func(idx int, src article2.Article) domain.Article {
			return c.toDomain(src)
		})

	//回写缓存，set 还是 del
	//set ,高并发 del
	go func() {
		err = c.cache.SetFirstPage(ctx, author, data)
		if err != nil {
			zap.L().Error("回写缓存失败:", zap.Error(err))
		}
		c.precache(ctx, data)
	}()
	return data, nil
}
func (c *CacheArticleRepository) ListPub(ctx context.Context, start time.Time, offset int, limit int) ([]domain.PublishedArticle, error) {
	val, err := c.dao.ListPub(ctx, start, offset, limit)
	if err != nil {
		return nil, err
	}
	return slice.Map[article2.PublishedArticle, domain.PublishedArticle](val, func(idx int, src article2.PublishedArticle) domain.PublishedArticle {
		// 偷懒写法
		return c.ToDomain(src)
	}), nil
}
func (c *CacheArticleRepository) precache(ctx context.Context, data []domain.Article) {
	const contentSizeThreshold = 1024 * 1024
	if len(data) > 0 && len(data[0].Content) <= contentSizeThreshold {
		if err := c.cache.Set(ctx, data[0]); err != nil {
			zap.L().Error("提前准备缓存失败:", zap.Error(err))
		}
	}
}
func (c *CacheArticleRepository) GetById(ctx context.Context, id int64) (domain.Article, error) {
	res, err := c.cache.Get(ctx, id)
	if err == nil {
		return res, nil
	}

	art, err := c.dao.GetById(ctx, id)
	if err != nil {
		return domain.Article{}, err
	}
	go func() {
		err = c.cache.Set(ctx, c.toDomain(art))
		if err != nil {
			zap.L().Error("回写缓存失败:", zap.Error(err))
		}
	}()
	return c.toDomain(art), nil
}
func (c *CacheArticleRepository) GetPublishById(ctx context.Context, id int64) (domain.PublishedArticle, error) {
	res, err := c.cache.GetPub(ctx, id)
	if err == nil {
		return res, nil
	}

	//读取线上库，如果你的content被你放过去oss,你就要让前端去读content
	art, err := c.dao.GetPublishById(ctx, id)
	if err != nil {
		return domain.PublishedArticle{}, err
	}
	// 组装user
	user, err := c.userRepo.FindById(ctx, art.AuthorId)
	if err != nil {
		return domain.PublishedArticle{}, err
	}

	res = c.ToDomain(art)
	res.Author.Name = user.Name

	go func() {
		if err = c.cache.SetPub(ctx, res); err != nil {
			zap.L().Error("缓存已发表文章失败", zap.Error(err), zap.Int64("aid", res.Id))
		}
	}()
	return res, nil
}
func (c *CacheArticleRepository) ToDomain(src article2.PublishedArticle) domain.PublishedArticle {
	return domain.PublishedArticle{
		Id:      src.Id,
		Title:   src.Title,
		Content: src.Content,
		Author: domain.Author{
			Id: src.AuthorId,
		},
		Status: domain.ArticleStatus(src.Status),
		Ctime:  time.UnixMilli(src.Ctime),
		Utime:  time.UnixMilli(src.Utime),
	}
}
func (c *CacheArticleRepository) toDomain(src article2.Article) domain.Article {
	return domain.Article{
		Id:      src.Id,
		Title:   src.Title,
		Content: src.Content,
		Author: domain.Author{
			Id: src.AuthorId,
		},
		Status: domain.ArticleStatus(src.Status),
		Ctime:  time.UnixMilli(src.Ctime),
		Utime:  time.UnixMilli(src.Utime),
	}
}
func (c *CacheArticleRepository) toEntity(art domain.Article) article2.Article {
	return article2.Article{
		Id:       art.Id,
		Title:    art.Title,
		Content:  art.Content,
		AuthorId: art.Author.Id,
		Status:   art.Status.ToUint8(),
	}
}
